package com.access.boot.apisdk.util;

import com.access.boot.apisdk.annotation.RequestMapping;
import com.access.boot.apisdk.annotation.RequestMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;

/**
 * @author YangZhen
 * @date 2022/6/17 14:32
 * @since 1.0.0
 */
public class HandlerMethod {

    private RequestMapping requestMapping;

    private RequestMethod requestMethod;

    private final Method method;

    private MethodParameter[] parameters;

    private final Class<?> beanType;

    private final String description;

    private final AnnotatedType returnType;

    private volatile List<Annotation[][]> interfaceParameterAnnotations;

    public HandlerMethod(Class<?> beanType, Method method) {
        this.method = method;
        this.beanType = beanType;
        this.parameters = initMethodParameters();
        getRequestMapping();
        this.description = initDescription(beanType, method);
        this.returnType = method.getAnnotatedReturnType();
    }

    public AnnotatedType getReturnType() {
        return this.returnType;
    }

    public void getRequestMapping() {
        Annotation[] annotations = this.method.getAnnotations();
        if (annotations != null) {
            for (Annotation annotation : annotations) {
                if (annotation instanceof RequestMapping) {
                    this.requestMapping = (RequestMapping) annotation;
                    this.requestMethod = this.requestMapping.method();
                    return;
                }
            }
        }
    }

    public String getOpenapi() {
        if (this.requestMapping != null) {
            return this.requestMapping.path();
        }
        return null;
    }

    public String getRequestMethod() {
        if (this.requestMethod != null) {
            return this.requestMethod.name();
        }
        return null;
    }

    @Override
    public String toString() {
        return this.description;
    }

    private MethodParameter[] initMethodParameters() {
        int count = this.method.getParameterCount();
        MethodParameter[] result = new MethodParameter[count];
        for (int i = 0; i < count; i++) {
            result[i] = new HandlerMethodParameter(i);
        }
        return result;
    }

    public MethodParameter[] getMethodParameters() {
        return this.parameters;
    }

    private static String initDescription(Class<?> beanType, Method method) {
        StringJoiner joiner = new StringJoiner(", ", "(", ")");
        for (Class<?> paramType : method.getParameterTypes()) {
            joiner.add(paramType.getSimpleName());
        }
        return beanType.getName() + "#" + method.getName() + joiner.toString();
    }

    public Class<?> getBeanType() {
        return beanType;
    }

    protected class HandlerMethodParameter extends MethodParameter {

        private volatile Annotation[] combinedAnnotations;

        public HandlerMethodParameter(int index) {
            super(HandlerMethod.this.method, index);
        }

        @Override
        public Annotation[] getParameterAnnotations() {
            Annotation[] anns = this.combinedAnnotations;
            if (anns == null) {
                anns = super.getParameterAnnotations();
                int index = getParameterIndex();
                if (index >= 0) {
                    for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) {
                        if (index < ifcAnns.length) {
                            Annotation[] paramAnns = ifcAnns[index];
                            if (paramAnns.length > 0) {
                                List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
                                merged.addAll(Arrays.asList(anns));
                                for (Annotation paramAnn : paramAnns) {
                                    boolean existingType = false;
                                    for (Annotation ann : anns) {
                                        if (ann.annotationType() == paramAnn.annotationType()) {
                                            existingType = true;
                                            break;
                                        }
                                    }
                                    if (!existingType) {
                                        merged.add(adaptAnnotation(paramAnn));
                                    }
                                }
                                anns = merged.toArray(new Annotation[0]);
                            }
                        }
                    }
                }
                this.combinedAnnotations = anns;
            }
            return anns;
        }

        @Override
        public Method getMethod() {
            return HandlerMethod.this.method;
        }

        @Override
        public Class<?> getContainingClass() {
            return HandlerMethod.this.getBeanType();
        }
    }

    private List<Annotation[][]> getInterfaceParameterAnnotations() {
        List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
        if (parameterAnnotations == null) {
            parameterAnnotations = new ArrayList<>();
            for (Class<?> ifc : ClassUtils.getAllInterfacesForClassAsSet(this.method.getDeclaringClass(), null)) {
                for (Method candidate : ifc.getMethods()) {
                    if (isOverrideFor(candidate)) {
                        parameterAnnotations.add(candidate.getParameterAnnotations());
                    }
                }
            }
            this.interfaceParameterAnnotations = parameterAnnotations;
        }
        return parameterAnnotations;
    }

    private boolean isOverrideFor(Method candidate) {
        if (!candidate.getName().equals(this.method.getName()) ||
                candidate.getParameterCount() != this.method.getParameterCount()) {
            return false;
        }
//        Class<?>[] paramTypes = this.method.getParameterTypes();
//        if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) {
//            return true;
//        }
//        for (int i = 0; i < paramTypes.length; i++) {
//            if (paramTypes[i] !=
//                    ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
//                return false;
//            }
//        }
        return true;
    }

    protected class ReturnValueMethodParameter extends HandlerMethodParameter {

        private final Class<?> returnValueType;

        public ReturnValueMethodParameter(Object returnValue) {
            super(-1);
            this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
        }

        @Override
        public ReturnValueMethodParameter clone() {
            return new ReturnValueMethodParameter(this);
        }
    }
}
